﻿/** \file
* \$Rev: 115 $
* 
* \$Date: 2011-04-16 02:05:17 +0000 (Sat, 16 Apr 2011) $
*
* \$URL: http://benfwaves.googlecode.com/svn/branches/fftw/BenfWaves.Library/Waveform.cs $
*/

using System;
using System.Collections.Generic;
using System.IO;
using System.Data.Linq.Mapping;
using System.Xml.Serialization;

using FFTW;

/// <summary>
/// The library containing conversion, serialization and file operations for
/// the BenF XML waveform format.
/// </summary>
namespace BenfWaves.Library
{
	/// <summary>A single waveform.</summary>
	[XmlRoot("Document")]
	public class Waveform
	{
		/// <summary>The XML serializer for this class.</summary>
		static XmlSerializer serializer = new XmlSerializer(typeof(Waveform));

		/// <summary>The spectrum obtained from FFTW.</summary>
		AlignedArrayComplex<double> spectrum;

		/// <summary>All attributes of the waveform.</summary>
		public Profile Profile { get; set; }

		/// <summary>The list of waveform data points.</summary>
		[XmlElement]
		public Point[] Point { get; set; }

		/// <summary>The first sample index in the waveform.</summary>
		[XmlIgnore]
		public int FirstIndex { get { return Point[0].seq; } }

		/// <summary>The last sample index in the waveform.</summary>
		[XmlIgnore]
		public int LastIndex { get { return Point[Point.Length - 1].seq; } }

		/// <summary>
		/// The series of voltages as a function of time.
		/// </summary>
		[XmlIgnore]
		public Series Voltages;
	
		/// <summary>
		/// The series of linear magnitudes as a function of frequency.
		/// </summary>
		[XmlIgnore]
		public Series SpectralLinMags;

		/// <summary>
		/// The series of logarithmic magnitudes as a function of frequency.
		/// </summary>
		[XmlIgnore]
		public Series SpectralLogMags;

		/// <summary>
		/// The series of phase angles as a function of frequency.
		/// </summary>
		[XmlIgnore]
		public Series SpectralPhases;

		/// <summary>
		/// Construct a Waveform by reading an XML file.
		/// </summary>
		/// <param name="filename">The name of the file to read.</param>
		/// <returns>The new WaveForm instance.</returns>
		public static Waveform FromXMLFile(string filename)
		{
			FileStream file = new FileStream(filename,
				FileMode.Open, FileAccess.Read);
			using (file)
			{
				Waveform newwave = (Waveform)serializer.Deserialize(file);
				newwave.Profile.filename = filename;
				newwave.Profile.parent = newwave;

				int seq = newwave.FirstIndex;
				foreach (Point point in newwave.Point)
				{
					if (point.seq != seq)
						throw new InvalidDataException(
							"The waveform data points are not contiguous.");
					seq++;
					point.Parent = newwave;
				}
				newwave.InitSeries();
				return newwave;
			}
		}

		/// <summary>
		/// Save this Waveform to an XML file.
		/// </summary>
		/// <param name="writer">The stream writer to write to.</param>
		public void ToXMLStream(TextWriter writer)
		{
			serializer.Serialize(writer, this);
		}

		/// <summary>
		/// Initialize the series data.
		/// </summary>
		void InitSeries()
		{
			GetSpectrum();

			VertSeriesDimension dimVoltage = new VertSeriesDimension();
			dimVoltage.GetUnitStart = () => -Profile.vdiffMaxDouble;
			dimVoltage.GetUnitRange = () => Profile.vdiffMaxDouble * 2;
			dimVoltage.GetNewSIValue = () => new SIVoltage();

			VertSeriesDimension dimSpectrumDb = new VertSeriesDimension();
			dimSpectrumDb.GetUnitStart = () => -100;
			dimSpectrumDb.GetUnitRange = () =>
				-dimSpectrumDb.GetUnitStart();
			dimSpectrumDb.GetNewSIValue = () => new SIdB();

			VertSeriesDimension dimSpectrumPhase = new VertSeriesDimension();
			dimSpectrumPhase.GetUnitStart = () => -180;
			dimSpectrumPhase.GetUnitRange = () =>
				-2 * dimSpectrumPhase.GetUnitStart();
			dimSpectrumPhase.GetNewSIValue = () => new SIAngle();

			HorzSeriesDimension dimTime = new HorzSeriesDimension();
			dimTime.GetUnitStart = () => Profile.timeStart.Value;
			dimTime.GetUnitRange = () => Profile.timeRangeDouble;
			dimTime.GetNewSIValue = () => new SITime();

			HorzSeriesDimension dimFreq = new HorzSeriesDimension();
			dimFreq.GetUnitStart = () => 0;
			dimFreq.GetUnitRange = () => Profile.samplingFreq.Value/2;
			dimFreq.GetNewSIValue = () => new SIFreq();
			
			Voltages = new Series(
				Array.ConvertAll(Point, p => p.val),
				dimTime, dimVoltage);

			SpectralLinMags = new Series(
				Array.ConvertAll(spectrum.Array,
					complex => complex.Magnitude / spectrum.Array.Length),
				dimFreq, dimVoltage);
			
			SpectralLogMags = new Series(
				Array.ConvertAll(spectrum.Array,
					complex => 20 * Math.Log10(
						complex.Magnitude / spectrum.Array.Length
						/ Profile.vdiffMaxDouble)),
				dimFreq, dimSpectrumDb);

			SpectralPhases = new Series(
				Array.ConvertAll(spectrum.Array,
					complex => complex.Phase),
				dimFreq, dimSpectrumPhase);
		}

		/// <summary>
		/// Generate the spectral data using FFTW.
		/// </summary>
		void GetSpectrum()
		{
			AlignedArrayReal<double> arrayIn = new AlignedArrayReal<double>(Point.Length);
			arrayIn.CopyFrom(Array.ConvertAll(Point,
				p => p.val));
			Plan_R2C<double> plan = new FFTW.Plan_R2C<double>(
				arrayIn, FFTW.PlanFlags.Estimate);
			plan.Execute();
			spectrum = plan.ArrayOut;
		}
	}

	/// <summary>A single data point in the waveform.</summary>
	[Table(Name = "Data_Raw$")]
	public class Point
	{
		/// <summary>The waveform to which this point belongs.</summary>
		[XmlIgnore]
		public Waveform Parent { get; set; }

		/// <summary>The index of the data point.</summary>
		[Column(IsPrimaryKey = true)]
		public int seq { get; set; }

		/// <summary>The value of the data point.</summary>
		[Column]
		public double val { get; set; }

		/// <summary>
		/// The time of this point, relative to the trigger index. Formatted
		/// as an SI quantity.
		/// </summary>
		[XmlIgnore]
		public SITime Time
		{
			get { return new SITime(TimeDouble); }
		}

		/// <summary>
		/// The time of this point, relative to the trigger index.
		/// </summary>
		[XmlIgnore]
		public double TimeDouble
		{
			get
			{
				return (seq - Parent.Profile.triggerIndex) /
					Parent.Profile.samplingFreq.Value;
			}
		}

		/// <summary>
		/// The voltage at this point, as an SI quantity.
		/// </summary>
		[XmlIgnore]
		public SIVoltage Voltage { get { return new SIVoltage(val); } }

		/// <summary>
		/// The parameterless constructor.
		/// </summary>
		public Point() { }

		/// <summary>
		/// A constructor for immediate initialization.
		/// </summary>
		/// <param name="seq">The point index.</param>
		/// <param name="val">The point voltage.</param>
		public Point(int seq, double val)
		{
			this.seq = seq;
			this.val = val;
		}
	}
}
